devlop
Some tools to make developing easier while not including code in production.
Contents
What is this?
This package lets you do things in development that are free in production.
It contains useful assert
functions and a deprecate
function that are
useful when developing JavaScript packages while being small in production.
If you know Rust, you might know how nice having a
debug_assert!
is.
This is that, and a bit more.
For more on why they’re nice, see
“Rust’s Two Kinds of ‘Assert’ Make for Better Code”
When should I use this?
Many JavaScript programs do not use assertions at all (perhaps because they’re
typed and so assume type safety) or include lots of code to throw errors when
users do weird things (weighing down production code).
This package hopes to improve the sitation by making assertions free and
deprecations cheap.
Install
This package is ESM only.
In Node.js (version 16+), install with npm:
npm install devlop
In Deno with esm.sh
:
import {deprecate, equal, ok, unreachable} from 'https://esm.sh/devlop@1'
In browsers with esm.sh
:
<script type="module">
import {deprecate, equal, ok, unreachable} from 'https://esm.sh/devlop@1?bundle'
</script>
Use
Say we have a small ponyfill for the ES5 String#includes
function.
It’s deprecated, because folks can use String#includes
nowadays.
It’s nicely typed so users should be able to figure out what to pass but we
include assertions to show nicer errors when they get it wrong.
example/string-includes.js
:
import {deprecate, ok} from 'devlop'
export const stringIncludes = deprecate(
includes,
'Since ES5, please use `String#includes` itself.'
)
function includes(value, search, position) {
ok(typeof value === 'string', 'expected string for `value`')
ok(typeof search === 'string', 'expected string for `search`')
ok(position === undefined || typeof position === 'number', 'expected number')
ok(
position === undefined ||
(typeof position === 'number' &&
!( Number.isNaN(position))),
'expected number'
)
return value.indexOf(search, position || 0) !== -1
}
example/index.js
:
import {stringIncludes} from './example-includes.js'
console.log(stringIncludes('blue whale', 'dolphin'))
console.log(stringIncludes('blue whale', 'whale'))
Say we’d bundle that in development with esbuild
and check the
gzip size (gzip-size-cli
), we’d get 1.02 kB of code:
$ esbuild example/index.js --bundle --conditions=development --format=esm --minify --target=es2022 | gzip-size
1.02 kB
But because devlop
is light in production we’d get:
$ esbuild example/index.js --bundle --format=esm --minify --target=es2022 | gzip-size
169 B
The bundle looks as follows:
function u(n){return n}var r=u(c,"Since ES5, please use `String#includes` itself.");function c(n,t,e){return n.indexOf(t,e||0)!==-1}console.log(r("blue whale","dolphin"));console.log(r("blue whale","whale"));
It depends a bit on which bundler and minifier you use how small the code is:
esbuild keeps the unused message parameter to the deprecate
function around
and does not know Number.isNaN
can be dropped without a /* #__PURE__ */
annotation.
rollup
with @rollup/plugin-node-resolve
and @rollup/plugin-terser
performs even better:
$ rollup example/index.js -p node-resolve -p terser | gzip-size
118 B
The bundle looks as follows:
const l=function(l,e,o){return-1!==l.indexOf(e,o||0)};console.log(l("blue whale","dolphin")),console.log(l("blue whale","whale"));
Rollup doesn’t need the /* #__PURE__ */
comment either!
API
This package exports the identifiers deprecate
,
equal
, ok
, and unreachable
.
There is no default export.
The export map supports the development
condition.
Run node --conditions development module.js
to get dev code.
Without this condition, no-ops are loaded.
deprecate(fn, message[, code])
Wrap a function or class to show a deprecation message when first called.
👉 Important: only shows a message when the development
condition is
used, does nothing in production.
When the resulting wrapped fn
is called, emits a warning once to
console.error
(stderr
).
If a code is given, one warning message will be emitted in total per code.
Parameters
fn
(Function
)
— function or classmessage
(string
)
— message explaining deprecationcode
(string
, optional)
— deprecation identifier (optional); deprecation messages will be generated
only once per code
Returns
Wrapped fn
.
equal(actual, expected[, message])
Assert deep strict equivalence.
👉 Important: only asserts when the development
condition is used, does
nothing in production.
Parameters
actual
(unknown
)
— valueexpected
(unknown
)
— baselinemessage
(Error
or string
, default: 'Expected values to be deeply equal'
)
— message for assertion error
Returns
Nothing (undefined
).
Throws
Throws (AssertionError
) when actual
is not deep strict equal to expected
.
ok(value[, message])
Assert if value
is truthy.
👉 Important: only asserts when the development
condition is used, does
nothing in production.
Parameters
actual
(unknown
)
— value to assertmessage
(Error
or string
, default: 'Expected value to be truthy'
)
— message for assertion error
Returns
Nothing (undefined
).
Throws
Throws (AssertionError
) when value
is falsey.
unreachable(message?)
Assert that a code path never happens.
👉 Important: only asserts when the development
condition is used,
does nothing in production.
Parameters
message
(Error
or string
, default: 'Unreachable'
)
— message for assertion error
Returns
Never (never
).
Throws
Throws (AssertionError
), always.
Types
This package is fully typed with TypeScript.
It exports no additional types.
Compatibility
This project is compatible with maintained versions of Node.js.
When we cut a new major release, we drop support for unmaintained versions of
Node.
This means we try to keep the current release line, devlop@^1
,
compatible with Node.js 16.
Security
This package is safe.
Related
babel-plugin-unassert
— encourage reliable programming with assertions while compiling them away
in production (can remove arbitrary assert
modules, works regardless of
conditions, so has to be configured by the end user)
Contribute
Yes please!
See How to Contribute to Open Source.
License
MIT © Titus Wormer